﻿#include "framework.h"
#include <WS2tcpip.h>
#include "Game.h"

// 函数声明
static void Game_Delete(Game*);
static void Game_AcceptInvite(Game*, LPCTSTR, BOOL);
static BOOL Game_CheckHorizontal(Game*);
static BOOL Game_CheckOblique1(Game*);
static BOOL Game_CheckOblique2(Game*);
static BOOL Game_CheckVertical(Game*);
static void Game_Defeat(Game*);
static void Game_DrawGameBoard(Game*);
static void Game_FindUsers(Game*);
static void Game_GameOver(Game*, GameOverReason);
static void Game_HandleFindUsers(Game*, FindUsersResult*);
static void Game_HandleGameOver(Game*, GameOver*);
static void Game_HandleInviteRequest(Game*, InviteRequest*);
static void Game_HandleInviteResult(Game*, InviteResult*);
static void Game_HandleLogon(Game*, GomokuProtocol*);
static void Game_HandlePutPiece(Game*, PutPiece*);
static void Game_HandleQuitGame(Game*, GomokuProtocol*);
static void Game_Invite(Game*, LPCTSTR);
static BOOL Game_IsWon(Game*);
static void Game_Logon(Game*, LPCTSTR);
static void Game_OnGameBoardClicked(Game*, int, int);
static void Game_OnGetMessageFromServer(Game*, GomokuProtocol*);
static BOOL Game_PutPiece(Game*, BOOL, int, int);
static DWORD WINAPI Game_ReceiveData(LPVOID);
static void Game_Restart(Game*);
static void Game_SendData(Game*, const void*, int);
static void Game_Start(Game*, BOOL);

/**
 * 创建游戏。
 * @param callback  回调
 * @param drawer    绘图者
 * @param gameBoard 游戏区域
 * @return 游戏
 */
Game* New_Game(HWND callback, HDC drawer, HWND gameBoard) {
    WORD version = MAKEWORD(2, 2);
    WSADATA wsaData = { 0 };
    SOCKADDR_IN serverAddr = { AF_INET };
    Game* game = (Game*)calloc(1, sizeof(Game));
    HANDLE threadHandle = NULL;
    int result = SOCKET_ERROR;

    // 初始化数据
    game->mBlackBrush = CreateSolidBrush(RGB(0, 0, 0));
    game->mCallback = callback;
    game->mDrawer = drawer;
    game->mGameBoard = gameBoard;
    game->mIsBlack = TRUE;
    game->mStatus = END;
    game->mWhiteBrush = CreateSolidBrush(RGB(255, 255, 255));

    // 初始化函数
    game->deleteSelf = Game_Delete;
    game->acceptInvite = Game_AcceptInvite;
    game->defeat = Game_Defeat;
    game->findUsers = Game_FindUsers;
    game->invite = Game_Invite;
    game->logon = Game_Logon;
    game->onGameBoardClicked = Game_OnGameBoardClicked;
    game->restart = Game_Restart;

    // 重绘游戏区域
    Game_DrawGameBoard(game);

    // 初始化网络
    WSAStartup(version, &wsaData);
    game->mSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // 连接服务器
    serverAddr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr);
    result = connect(game->mSocket, (SOCKADDR*)&serverAddr, sizeof(SOCKADDR_IN));
    if (result == SOCKET_ERROR) {
        return NULL;
    }

    // 开启接收数据的线程
    threadHandle = CreateThread(NULL, 0, Game_ReceiveData, game, 0, NULL);
    CloseHandle(threadHandle);

    return game;
}

/**
 * 删除游戏。
 * @param game  游戏
 */
void Game_Delete(Game* game) {
    // 发送退出游戏请求
    GomokuProtocol* quitGame = New_GomokuProtocol(QUIT_GAME, game->mUsername);
    Game_SendData(game, quitGame, sizeof(GomokuProtocol));
    free(quitGame);
    
    // 延迟一下，防止数据还没发送完成就关闭网络
    Sleep(100);

    // 关闭网络
    closesocket(game->mSocket);
    WSACleanup();

    // 删除成员
    DeleteObject(game->mBlackBrush);
    DeleteObject(game->mWhiteBrush);
}

/**
 * 接受或拒绝挑战。
 * @param game      游戏
 * @param rivalName 对手的名字
 * @param accept    接受挑战则为 TRUE，否则为 FALSE
 */
void Game_AcceptInvite(Game* game, LPCTSTR rivalName, BOOL accept) {
    InviteResult* result = New_InviteResult(accept, rivalName, game->mUsername);

    // 如果接受则游戏开始
    if (accept) {
        _tcscpy_s(game->mRivalName, USERNAME_BUF_LEN, rivalName);
        Game_Start(game, FALSE);
    }

    // 发送结果
    Game_SendData(game, result, sizeof(InviteResult));
}

/**
 * 检查横向。
 * @param game  游戏
 * @return 胜利则返回 TRUE，否则返回 FALSE
 */
BOOL Game_CheckHorizontal(Game* game) {
    Piece checkPiece = game->mIsBlack ? BLACK: WHITE;  // 要检查的棋子
    int count = 0;                                      // 连续的棋子的数量
    int i;

    // 从左到右检查一行
    for (i = 0; i < GAME_BOARD_POINT_COUNT_H; i++) {
        // 查找棋子
        if (game->mPieces[game->mLastPos.y][i] == checkPiece) {
            count++;
        } else {
            count = 0;
        }

        // 如果5个棋子连在一起就胜利
        if (count == WIN_NUMBER) {
            return TRUE;
        }
    }
    return FALSE;
}

/**
 * 检查斜向（从左上到右下）。
 * @param game  游戏
 * @return 胜利则返回 TRUE，否则返回 FALSE
 */
BOOL Game_CheckOblique1(Game* game) {
    Piece checkPiece = game->mIsBlack ? BLACK: WHITE;                                      // 要检查的棋子
    int count = 0;                                                                          // 连续的棋子的数量
    int min = game->mLastPos.x < game->mLastPos.y ? game->mLastPos.x: game->mLastPos.y;    // 范围坐标，用以确定左边界和上边界
    int left = game->mLastPos.x - min;                                                      // 左边界
    int top = game->mLastPos.y - min;                                                       // 上边界
    int i, j;                                                                               // 循环变量

    // 从左上到右下检查一斜行
    for (i = left, j = top; i < GAME_BOARD_POINT_COUNT_H && j < GAME_BOARD_POINT_COUNT_V; i++, j++) {
        // 查找棋子
        if (game->mPieces[j][i] == checkPiece) {
            count++;
        } else {
            count = 0;
        }

        // 如果5个棋子连在一起就胜利
        if (count == WIN_NUMBER) {
            return TRUE;
        }
    }
    return FALSE;
}

/**
 * 检查斜向（从左下到右上）。
 * @param game  游戏
 * @return 胜利则返回 TRUE，否则返回 FALSE
 */
BOOL Game_CheckOblique2(Game* game) {
    Piece checkPiece = game->mIsBlack ? BLACK: WHITE;  // 要检查的棋子
    int count = 0;                                      // 连续的棋子的数量
    int left = 0;                                       // 左边界
    int bottom = 0;                                     // 下边界
    int i, j;                                           // 循环变量

    // 计算左边界和下边界
    if (game->mLastPos.x + game->mLastPos.y < GAME_BOARD_POINT_COUNT_H) {
        bottom = game->mLastPos.x + game->mLastPos.y;
    } else {
        left = game->mLastPos.x + game->mLastPos.y - GAME_BOARD_POINT_COUNT_H + 1;
        bottom = GAME_BOARD_POINT_COUNT_V - 1;
    }

    // 从左上到右上检查一斜行
    for (i = left, j = bottom; i < GAME_BOARD_POINT_COUNT_H && j >= 0; i++, j--) {
        // 查找棋子
        if (game->mPieces[j][i] == checkPiece) {
            count++;
        } else {
            count = 0;
        }

        // 如果5个棋子连在一起就胜利
        if (count == WIN_NUMBER) {
            return TRUE;
        }
    }
    return FALSE;
}

/**
 * 检查纵向。
 * @param game  游戏
 * @return 胜利则返回 TRUE，否则返回 FALSE
 */
BOOL Game_CheckVertical(Game* game) {
    Piece checkPiece = game->mIsBlack ? BLACK: WHITE;  // 要检查的棋子
    int count = 0;                                      // 连续的棋子的数量
    int j;

    // 从上到下检查一列
    for (j = 0; j < GAME_BOARD_POINT_COUNT_V; j++) {
        // 查找棋子
        if (game->mPieces[j][game->mLastPos.x] == checkPiece) {
            count++;
        } else {
            count = 0;
        }

        // 如果5个棋子连在一起就胜利
        if (count == WIN_NUMBER) {
            return TRUE;
        }
    }
    return FALSE;
}

/**
 * 认输。
 * @param game  游戏
 */
void Game_Defeat(Game* game) {
    // 发送游戏结束数据
    GameOver* gameOver = New_GameOver(SELF_ADMIT, game->mRivalName);
    Game_SendData(game, gameOver, sizeof(GameOver));
    free(gameOver);

    // 游戏结束
    Game_GameOver(game, SELF_ADMIT);
}

/**
 * 画游戏区域。
 * @param game  游戏
 */
void Game_DrawGameBoard(Game* game) {
    RECT rect = { 0, 0, GAME_BOARD_WIDTH, GAME_BOARD_HEIGHT };

    // 画背景色
    SetBkColor(game->mDrawer, GAME_BOARD_COLOR);
    ExtTextOut(game->mDrawer, 0, 0, ETO_OPAQUE, &rect, NULL, 0, NULL);

    // 画线
    for (int i = 1; i <= GAME_BOARD_POINT_COUNT_H; i++) {
        // 画横线
        MoveToEx(game->mDrawer, GRID_SIZE, i * GRID_SIZE, NULL);
        LineTo(game->mDrawer, GAME_BOARD_WIDTH - GRID_SIZE, i * GRID_SIZE);

        // 画竖线
        MoveToEx(game->mDrawer, i * GRID_SIZE, GRID_SIZE, NULL);
        LineTo(game->mDrawer, i * GRID_SIZE, GAME_BOARD_HEIGHT - GRID_SIZE);
    }

    // 通知游戏区域重绘
    InvalidateRect(game->mGameBoard, NULL, FALSE);
}

/**
 * 寻找其它用户。
 * @param game  游戏
 */
void Game_FindUsers(Game* game) {
    GomokuProtocol* request = NULL;

    if (game->mUsername[0] == '\0') {
        return;
    }

    request = New_GomokuProtocol(FIND_USERS, game->mUsername);
    Game_SendData(game, request, sizeof(GomokuProtocol));
    free(request);
}

/**
 * 游戏结束。
 * @param game      游戏
 * @param reason    结束原因
 */
void Game_GameOver(Game* game, GameOverReason reason) {
    // 将状态设置为已结束
    game->mStatus = END;

    // 发送通知
    SendMessage(game->mCallback, UM_GAME_OVER, reason, 0);
}

/**
 * 处理寻找其它用户结果。
 * @param game      游戏
 * @param result    结果
 */
void Game_HandleFindUsers(Game* game, FindUsersResult* result) {
    // 发送通知
    SendMessage(game->mCallback, UM_GET_FIND_USERS_RESULT, (WPARAM)result, 0);
}

/**
 * 处理游戏结束。
 * @param game      游戏
 * @param gameOver  游戏结束信息
 */
void Game_HandleGameOver(Game* game, GameOver* gameOver) {
    GameOverReason reason = gameOver->mReason;

    // 如果没在下棋中，则直接返回
    if (game->mStatus == END) {
        return;
    }

    // 将状态设置为已结束
    game->mStatus = END;

    // 发送通知
    if (reason == WON) {
        reason = LOST;
    } else if (reason == SELF_ADMIT) {
        reason = RIVAL_ADMIT;
    }
    SendMessage(game->mCallback, UM_GAME_OVER, reason, 0);
}

/**
 * 处理挑战请求。
 * @param game      游戏
 * @param request   请求
 */
void Game_HandleInviteRequest(Game* game, InviteRequest* request) {
    // 发送通知
    SendMessage(game->mCallback, UM_GET_INVITE_REQUEST, (WPARAM)request->mRivalName, 0);
}

/**
 * 处理挑战结果。
 * @param game      游戏
 * @param result    结果
 */
void Game_HandleInviteResult(Game* game, InviteResult* result) {
    // 如果接受则游戏开始
    if (result->mAccept) {
        _tcscpy_s(game->mRivalName, USERNAME_BUF_LEN, result->mRivalName);
        Game_Start(game, TRUE);
    }

    // 发送通知
    SendMessage(game->mCallback, UM_GET_INVITE_RESULT, (WPARAM)result->mRivalName, (LPARAM)result->mAccept);
}

/**
 * 处理登录结果。
 * @param game      游戏
 * @param result    结果
 */
void Game_HandleLogon(Game* game, GomokuProtocol* result) {
    BOOL succeeded = result->mUsername[0] != '\0';
    if (succeeded) {
        // 保存用户名
        _tcscpy_s(game->mUsername, USERNAME_BUF_LEN, result->mUsername);
    }

    // 发送通知
    SendMessage(game->mCallback, UM_GET_LOGON_RESULT, (WPARAM)succeeded, 0);
}

/**
 * 处理放置棋子。
 * @param game      游戏
 * @param putPiece  棋子信息
 */
void Game_HandlePutPiece(Game* game, PutPiece* putPiece) {
    // 放一个与自己颜色相反的棋子
    Game_PutPiece(game, !game->mIsBlack, putPiece->mX, putPiece->mY);

    // 将状态设置为游戏中
    game->mStatus = PLAYING;
}

/**
 * 处理对手退出游戏事件。
 * @param game      游戏
 * @param quitGame  退出游戏信息
 */
void Game_HandleQuitGame(Game* game, GomokuProtocol* quitGame) {
    BOOL isPlaying = game->mStatus != END;

    // 如果不是当前对手，则直接返回
    if (_tcscmp(game->mRivalName, quitGame->mUsername) != 0) {
        return;
    }

    // 发送通知
    SendMessage(game->mCallback, UM_RIVAL_QUIT_GAME, isPlaying, (LPARAM)quitGame->mUsername);

    // 对手名置空
    ZeroMemory(&game->mRivalName, sizeof(game->mRivalName));

    // 将状态设置为已结束
    game->mStatus = END;
}

/**
 * 发起挑战。
 * @param game      游戏
 * @param rivalName 要挑战的用户的名字
 */
void Game_Invite(Game* game, LPCTSTR rivalName) {
    InviteRequest* request = NULL;

    if (rivalName == NULL) {
        return;
    }

    // 发送挑战请求
    request = New_InviteRequest(game->mUsername, rivalName);
    Game_SendData(game, request, sizeof(InviteRequest));
    free(request);
}


/**
 * 判断是否胜利。
 * @param game  游戏
 * @return 胜利则返回 TRUE，否则返回 FALSE
 */
BOOL Game_IsWon(Game* game) {
    // 检查横向
    BOOL result = Game_CheckHorizontal(game);
    if (result) {
        return result;
    }

    // 检查纵向
    result = Game_CheckVertical(game);
    if (result) {
        return result;
    }

    // 检查斜向（从左上到右下）
    result = Game_CheckOblique1(game);
    if (result) {
        return result;
    }

    // 检查斜向（从左下到右上）
    return Game_CheckOblique2(game);
}

/**
 * 登录。
 * @param game      游戏
 * @param username  用户名
 */
void Game_Logon(Game* game, LPCTSTR username) {
    // 发送登录请求
    GomokuProtocol* request = New_GomokuProtocol(LOGON, username);
    Game_SendData(game, request, sizeof(GomokuProtocol));
    free(request);
}

/**
 * 游戏区域的单击响应方法。
 * @param game  游戏
 * @param x, y  用户单击的位置
 */
void Game_OnGameBoardClicked(Game* game, int x, int y) {
    int bound = GRID_SIZE / 2;   // 格的尺寸的一半
    int temp = x % GRID_SIZE;
    BOOL put = FALSE;
    PutPiece* putPiece;
    BOOL won = FALSE;
    GameOver* gameOver = NULL;
    int tempX = x;
    int tempY = y;

    // 如果不是己方下棋中（对手下棋中或游戏结束）或者 单击的范围超出棋盘，则直接返回
    if (game->mStatus != PLAYING ||
        x < bound || x > GAME_BOARD_WIDTH - bound ||
        y < bound || y > GAME_BOARD_HEIGHT - bound) {
        return;
    }

    // 将用户单击的点转换为离得最近的棋盘点
    if (temp >= bound) {
        tempX += GRID_SIZE - temp;
    } else {
        tempX -= temp;
    }

    temp = y % GRID_SIZE;
    if (temp >= bound) {
        tempY += GRID_SIZE - temp;
    } else {
        tempY -= temp;
    }

    // 将窗口坐标转换成棋盘坐标
    tempX = tempX / GRID_SIZE - 1;
    tempY = tempY / GRID_SIZE - 1;

    // 放置棋子
    put = Game_PutPiece(game, game->mIsBlack, tempX, tempY);
    if (!put) {
        return;
    }

    // 判断是否胜利
    won = Game_IsWon(game);

    // 将已放置的棋子发给对手
    putPiece = New_PutPiece(game->mRivalName, tempX, tempY);
    Game_SendData(game, putPiece, sizeof(PutPiece));
    free(putPiece);

    // 如果胜利则通知对手，否则等待对手下棋
    if (won) {
        // 发送游戏结束数据
        gameOver = New_GameOver(WON, game->mRivalName);
        _tcscpy_s(gameOver->mRivalName, USERNAME_BUF_LEN, game->mRivalName);
        Game_SendData(game, gameOver, sizeof(GameOver));
        free(gameOver);

        // 游戏结束
        Game_GameOver(game, WON);
    } else {
        game->mStatus = RIVAL_PLAYING;
    }
}

/**
 * 处理服务器发来的数据。
 * @param game  游戏
 * @param pro   协议
 */
static void Game_OnGetMessageFromServer(Game* game, GomokuProtocol* pro) {
    switch (pro->mType) {
        case FIND_USERS:
            Game_HandleFindUsers(game, (FindUsersResult*)pro);
            break;
        case GAME_OVER:
            Game_HandleGameOver(game, (GameOver*)pro);
            break;
        case INVITE_REQUEST:
            Game_HandleInviteRequest(game, (InviteRequest*)pro);
            break;
        case INVITE_RESULT:
            Game_HandleInviteResult(game, (InviteResult*)pro);
            break;
        case LOGON:
            Game_HandleLogon(game, pro);
            break;
        case PUT_PIECE:
            Game_HandlePutPiece(game, (PutPiece*)pro);
            break;
        case QUIT_GAME:
            Game_HandleQuitGame(game, pro);
            break;
    }
}

/**
 * 放置棋子。
 * @param game      游戏
 * @param isBlack   黑棋则为 TRUE，白棋则为 FALSE
 * @param x, y      棋盘坐标
 * @return 成功则返回 TRUE，否则返回 FALSE
 */
BOOL Game_PutPiece(Game* game, BOOL isBlack, int x, int y) {
    // 如果超出棋盘区域，则返回 false
    if (x >= GAME_BOARD_POINT_COUNT_H || y >= GAME_BOARD_POINT_COUNT_V) {
        return FALSE;
    }

    // 如果当前点已经有棋子了，则返回 FALSE
    if (game->mPieces[y][x] != NOTHING) {
        return FALSE;
    }

    // 画实心圆
    if (isBlack) {
        SelectObject(game->mDrawer, game->mBlackBrush);
    } else {
        SelectObject(game->mDrawer, game->mWhiteBrush);
    }
    Ellipse(game->mDrawer, 
        (x + 1) * GRID_SIZE - PIECE_RADIUS, (y + 1) * GRID_SIZE - PIECE_RADIUS, 
        (x + 1) * GRID_SIZE + PIECE_RADIUS, (y + 1) * GRID_SIZE + PIECE_RADIUS);

    // 通知游戏区域重绘
    InvalidateRect(game->mGameBoard, NULL, FALSE);

    // 保存最后放置棋子的位置
    game->mLastPos.x = x;
    game->mLastPos.y = y;

    // 保存到所有棋子中
    if (isBlack) {
        game->mPieces[y][x] = BLACK;
    } else {
        game->mPieces[y][x] = WHITE;
    }

    return TRUE;
}

/**
 * 接收数据。
 * @param param 游戏
 * @return 总是零
 */
static DWORD WINAPI Game_ReceiveData(LPVOID param) {
    Game* game = (Game*)param;  // 游戏
    int dataSize = 0;           // 数据长度
    char* buffer = NULL;        // 接收数据的缓存
    int read = 1;               // 接收到的数据长度
    GomokuProtocol* pro = NULL; // 协议

    // 接收数据
    while (read > 0) {
        // 接收数据长度
        read = recv(game->mSocket, (char*)&dataSize, sizeof(dataSize), 0);
        if (read == SOCKET_ERROR) {
            break;
        }

        // 接收数据
        buffer = (char*)calloc(1, dataSize);
        read = recv(game->mSocket, buffer, dataSize, 0);
        if (read <= 0) {
            free(buffer);
            break;
        }

        // 将数据转换成协议
        pro = (GomokuProtocol*)buffer;

        // 处理协议
        Game_OnGetMessageFromServer(game, pro);
    }

    return 0;
}

/**
 * 游戏重新开始。
 * @param game  游戏
 */
void Game_Restart(Game* game) {
    // 发送挑战请求
    InviteRequest* request = New_InviteRequest(game->mUsername, game->mRivalName);
    Game_SendData(game, request, sizeof(InviteRequest));
    free(request);
}

/**
 * 发送数据。
 * @param game      游戏
 * @param pro       协议
 * @param dataSize  数据长度
 */
static void Game_SendData(Game* game, const void* pro, int dataSize) {
    // 发送数据长度
    send(game->mSocket, (const char*)&dataSize, sizeof(dataSize), 0);

    // 发送数据
    send(game->mSocket, pro, dataSize, 0);

    // 防止发送过快导致服务器无法响应
    Sleep(100);
}

/**
 * 游戏开始。
 * @param game      游戏
 * @param isBlack   黑棋则为 TRUE，白棋则为 FALSE
 */
void Game_Start(Game* game, BOOL isBlack) {
    // 重绘游戏区域
    Game_DrawGameBoard(game);

    // 清空所有棋子
    ZeroMemory(game->mPieces, sizeof(game->mPieces));

    // 设置成员变量
    game->mStatus = isBlack ? PLAYING: RIVAL_PLAYING;
    game->mIsBlack = isBlack;

    // 发送通知
    SendMessage(game->mCallback, UM_GAME_START, (WPARAM)game->mRivalName, 0);
}
